#define MODULE_LOG_PREFIX "reader"

#include "globals.h"
#include "module-cccam.h"
#include "module-led.h"
#include "module-stat.h"
#include "module-dvbapi.h"
#include "oscam-cache.h"
#include "oscam-chk.h"
#include "oscam-client.h"
#include "oscam-ecm.h"
#include "oscam-garbage.h"
#include "oscam-lock.h"
#include "oscam-net.h"
#include "oscam-reader.h"
#include "oscam-string.h"
#include "oscam-time.h"
#include "oscam-work.h"
#include "reader-common.h"
#include "oscam-config.h"

extern CS_MUTEX_LOCK system_lock;
extern CS_MUTEX_LOCK ecmcache_lock;
extern struct ecm_request_t *ecmcwcache;
extern const struct s_cardsystem *cardsystems[];

const char *RDR_CD_TXT[] =
{
	"cd", "dsr", "cts", "ring", "none",
	"gpio1", "gpio2", "gpio3", "gpio4", "gpio5", "gpio6", "gpio7",
	NULL
};

/* Overide ratelimit priority for dvbapi request */
static int32_t dvbapi_override_prio(struct s_reader *reader, ECM_REQUEST *er, int32_t maxecms, struct timeb *actualtime)
{
	if (!module_dvbapi_enabled() || !is_dvbapi_usr(er->client->account->usr))
		return -1;

	int32_t foundspace = -1;
	int64_t gone = 0;

	if (reader->lastdvbapirateoverride.time == 0) // fixup for first run!
		gone = comp_timeb(actualtime, &reader->lastdvbapirateoverride);

	if (gone > reader->ratelimittime)
	{
		int32_t h;
		struct timeb minecmtime = *actualtime;

		for (h = 0; h < MAXECMRATELIMIT; h++)
		{
			gone = comp_timeb(&minecmtime, &reader->rlecmh[h].last);
			if (gone > 0) {
				minecmtime = reader->rlecmh[h].last;
				foundspace = h;
			}
		}
		reader->lastdvbapirateoverride = *actualtime;

		cs_log_dbg(D_CLIENT, "prioritizing DVBAPI user %s over other watching client",
					er->client->account->usr);

		cs_log_dbg(D_CLIENT, "ratelimiter forcing srvid %04X into slot %d/%d of reader %s",
					er->srvid, foundspace + 1, maxecms, reader->label);
	}
	else
	{
		cs_log_dbg(D_CLIENT, "DVBAPI User %s is switching too fast for ratelimit and can't be prioritized!",
					er->client->account->usr);
	}
	return foundspace;
}

static int32_t ecm_ratelimit_findspace(struct s_reader *reader, ECM_REQUEST *er, struct ecmrl rl, int32_t reader_mode)
{
	int32_t h, foundspace = -1;
	int32_t maxecms = MAXECMRATELIMIT; // init maxecms
	int32_t totalecms = 0; // init totalecms
	struct timeb actualtime;
	cs_ftime(&actualtime);

	for(h = 0; h < MAXECMRATELIMIT; h++) // release slots with srvid that are overtime, even if not called from reader module to maximize available slots!
	{
		if(reader->rlecmh[h].last.time == -1) { continue; }

		int64_t gone = comp_timeb(&actualtime, &reader->rlecmh[h].last);
		if(gone >= (reader->rlecmh[h].ratelimittime + reader->rlecmh[h].srvidholdtime) || gone < 0) // gone <0 fixup for bad systemtime on dvb receivers while changing transponders
		{
			cs_log_dbg(D_CLIENT, "ratelimiter srvid %04X released from slot %d/%d of reader %s (%"PRId64">=%d ratelimit ms + %d ms srvidhold!)",
						reader->rlecmh[h].srvid, h + 1, MAXECMRATELIMIT, reader->label, gone,
						reader->rlecmh[h].ratelimittime, reader->rlecmh[h].srvidholdtime);

			reader->rlecmh[h].last.time = -1;
			reader->rlecmh[h].srvid = -1;
			reader->rlecmh[h].kindecm = 0;
			reader->rlecmh[h].once = 0;
		}

		if(reader->rlecmh[h].last.time == -1) { continue; }
		if(reader->rlecmh[h].ratelimitecm < maxecms) { maxecms = reader->rlecmh[h].ratelimitecm; } // we found a more critical ratelimit srvid
		totalecms++;
	}

	cs_log_dbg(D_CLIENT, "ratelimiter found total of %d srvid for reader %s most critical is limited to %d requests",
				totalecms, reader->label, maxecms);

	if(reader->cooldown[0] && reader->cooldownstate != 1) { maxecms = MAXECMRATELIMIT; } // dont apply ratelimits if cooldown isnt in use or not in effect

	for(h = 0; h < MAXECMRATELIMIT; h++) // check if srvid is already in a slot
	{
		if(reader->rlecmh[h].last.time == -1) { continue; }

		if(reader->rlecmh[h].srvid == er->srvid && reader->rlecmh[h].caid == rl.caid && reader->rlecmh[h].provid == rl.provid
			&& (!reader->rlecmh[h].chid || (reader->rlecmh[h].chid == rl.chid)))
		{
			int64_t gone = 0;
#ifdef WITH_DEBUG
			if(cs_dblevel & D_CLIENT)
			{
				gone = comp_timeb(&actualtime, &reader->rlecmh[h].last);
				cs_log_dbg(D_CLIENT, "ratelimiter found srvid %04X for %"PRId64" ms in slot %d/%d of reader %s", er->srvid, gone, h + 1, MAXECMRATELIMIT, reader->label);
			}
#endif
			// check ecmunique if enabled and ecmunique time is done
			if(reader_mode && reader->ecmunique)
			{
				gone = comp_timeb(&actualtime, &reader->rlecmh[h].last);
				if(gone < reader->ratelimittime)
				{
					// some boxes seem to send different ecms but asking in fact for same cw!
					if(memcmp(reader->rlecmh[h].ecmd5, er->ecmd5, CS_ECMSTORESIZE))
					{
						// different ecm request than one in the slot!
						if(er->ecm[0] == reader->rlecmh[h].kindecm)
						{
							// same ecm type!
#ifdef WITH_DEBUG
							if(cs_dblevel & D_CLIENT)
							{
								char ecmd5[17 * 3];
								cs_hexdump(0, reader->rlecmh[h].ecmd5, 16, ecmd5, sizeof(ecmd5));
								cs_log_dbg(D_CLIENT, "ratelimiter ecm %s in this slot for next %d ms!", ecmd5,
											(int)(reader->rlecmh[h].ratelimittime - gone));
							}
#endif
							struct ecm_request_t *erold = NULL;
							if(!cs_malloc(&erold, sizeof(struct ecm_request_t)))
								{ return -2; }

							memcpy(erold, er, sizeof(struct ecm_request_t)); // copy ecm all
							memcpy(erold->ecmd5, reader->rlecmh[h].ecmd5, CS_ECMSTORESIZE); // replace md5 hash
							struct ecm_request_t *ecm = NULL;
							ecm = check_cache(erold, erold->client); //CHECK IF FOUND ECM IN CACHE
							NULLFREE(erold);

							if(ecm) // found in cache
							{
								// return controlword of the ecm sitting in the slot!
								write_ecm_answer(reader, er, ecm->rc, ecm->rcEx, ecm->cw, NULL, 0, &ecm->cw_ex);
							}
							else
							{
								write_ecm_answer(reader, er, E_NOTFOUND, E2_RATELIMIT, NULL, "Ratelimiter: no slots free!", 0, NULL);
							}

							NULLFREE(ecm);
							return -2;
						}
					}
				}

				if((er->ecm[0] != reader->rlecmh[h].kindecm) && (gone <= reader->ratelimittime))
				{
					if(!reader->rlecmh[h].once) // 1 premature ecmtype change is allowed (useful right after zapping to a channel!)
					{
						reader->rlecmh[h].once = 1;
						cs_log_dbg(D_CLIENT, "ratelimiter changing slot %d srvid %04X ecmtype once from %s to %s!", h+1, er->srvid,
							(reader->rlecmh[h].kindecm == 0x80 ? "even":"odd"), (er->ecm[0] == 0x80 ? "even":"odd"));
					}
					else
					{
						cs_log_dbg(D_CLIENT, "ratelimiter srvid %04X only allowing ecmtype %s for next %d ms in slot %d/%d of reader %s -> skipping this slot!",
									reader->rlecmh[h].srvid, (reader->rlecmh[h].kindecm == 0x80 ? "even" : "odd"),
									(int)(reader->rlecmh[h].ratelimittime - gone), h + 1, maxecms, reader->label);
							continue;
					}
				}
			}

			if(h > 0)
			{
				for(foundspace = 0; foundspace < h; foundspace++) // check for free lower slot
				{
					if(reader->rlecmh[foundspace].last.time == -1)
					{
						reader->rlecmh[foundspace] = reader->rlecmh[h]; // replace ecm request info
						reader->rlecmh[h].last.time = -1;
						reader->rlecmh[h].srvid = -1;
						reader->rlecmh[h].kindecm = 0;
						reader->rlecmh[h].once = 0;

						if(foundspace < maxecms)
						{
							cs_log_dbg(D_CLIENT, "ratelimiter moved srvid %04X to slot %d/%d of reader %s",
										er->srvid, foundspace + 1, maxecms, reader->label);

							return foundspace; // moving to lower free slot!
						}
						else
						{
							cs_log_dbg(D_CLIENT, "ratelimiter removed srvid %04X from slot %d/%d of reader %s",
										er->srvid, foundspace + 1, maxecms, reader->label);

							reader->rlecmh[foundspace].last.time = -1; // free this slot since we are over ratelimit!
							return -1; // sorry, ratelimit!
						}
					}
				}
			}

			if(h < maxecms) // found but cant move to lower position!
			{
				return h; // return position if within ratelimits!
			}
			else
			{
				reader->rlecmh[h].last.time = -1; // free this slot since we are over ratelimit!
				reader->rlecmh[h].srvid = -1;
				reader->rlecmh[h].kindecm = 0;
				reader->rlecmh[h].once = 0;

				cs_log_dbg(D_CLIENT, "ratelimiter removed srvid %04X from slot %d/%d of reader %s",
							er->srvid, h + 1, maxecms, reader->label);

				return -1; // sorry, ratelimit!
			}
		}
	}

	// srvid not found in slots!

	// do we use cooldown at all, are we in cooldown fase?
	if((reader->cooldown[0] && reader->cooldownstate == 1) || !reader->cooldown[0])
	{
		// we are in cooldown or no cooldown configured!
		if(totalecms + 1 > maxecms || totalecms + 1 > rl.ratelimitecm) // check if this channel fits in!
		{
			cs_log_dbg(D_CLIENT, "ratelimiter for reader %s has no free slots!", reader->label);
			return -1;
		}
	}
	else
	{
		maxecms = MAXECMRATELIMIT; // no limits right now!
	}

	for(h = 0; h < maxecms; h++) // check for free slot
	{
		if(reader->rlecmh[h].last.time == -1)
		{
			if(reader_mode)
			{
				cs_log_dbg(D_CLIENT, "ratelimiter added srvid %04X to slot %d/%d of reader %s",
							er->srvid, h + 1, maxecms, reader->label);
			}
			return h; // free slot found -> assign it!
		}
		else // occupied slots
		{
#ifdef WITH_DEBUG
			if(cs_dblevel & D_CLIENT)
			{
				int64_t gone = comp_timeb(&actualtime, &reader->rlecmh[h].last);
				cs_log_dbg(D_CLIENT, "ratelimiter srvid %04X for %"PRId64" ms present in slot %d/%d of reader %s",
							reader->rlecmh[h].srvid, gone , h + 1, maxecms, reader->label);
			}
#endif
		}
	}

	foundspace = dvbapi_override_prio(reader, er, maxecms, &actualtime);
	if (foundspace > -1)
		return foundspace;

	return (-1); // no slot found
}

static void sort_ecmrl(struct s_reader *reader)
{
	int32_t i, j, loc;
	struct ecmrl tmp;

	for(i = 0; i < reader->ratelimitecm; i++) // inspect all slots
	{
		if(reader->rlecmh[i].last.time == -1) { continue; } // skip empty slots

		loc = i;
		tmp = reader->rlecmh[i]; // tmp is ecm in slot to evaluate

		for(j = i + 1; j < MAXECMRATELIMIT; j++) // inspect all slots above the slot to be inspected
		{
			if(reader->rlecmh[j].last.time == -1) { continue; } // skip empty slots

			int32_t gone = comp_timeb(&reader->rlecmh[i].last, &tmp.last);
			if(gone > 0) // is higher slot holding a younger ecmrequest?
			{
				loc = j; // found a younger one
				tmp = reader->rlecmh[j]; // copy the ecm in younger slot
			}
		}

		if(loc != i) // Did we find a younger ecmrequest?
		{
			reader->rlecmh[loc] = reader->rlecmh[i]; // place older request in slot of younger one we found
			reader->rlecmh[i] = tmp; // place younger request in slot of older request
		}
	}

	// release all slots above ratelimit ecm
	for(i = reader->ratelimitecm; i < MAXECMRATELIMIT; i++)
	{
		reader->rlecmh[i].last.time = -1;
		reader->rlecmh[i].srvid = -1;
		reader->rlecmh[i].kindecm = 0;
		reader->rlecmh[i].once = 0;
	}

}

// If reader_mode is 1, ECM_REQUEST need to be assigned to reader and slot.
// Else just report if a free slot is available.
int32_t maxslots = MAXECMRATELIMIT;
int32_t ecm_ratelimit_check(struct s_reader *reader, ECM_REQUEST *er, int32_t reader_mode)
{
	// No rate limit set
	if(!reader->ratelimitecm)
	{
		return OK;
	}

	int32_t foundspace = -1, h; // init slots to oscam global maximums
	struct ecmrl rl;
	struct timeb now;
	rl = get_ratelimit(er);

	if(rl.ratelimitecm > 0)
	{
		cs_log_dbg(D_CLIENT, "ratelimit found for CAID: %04X PROVID: %06X SRVID: %04X CHID: %04X maxecms: %d cycle: %d ms srvidhold: %d ms",
					rl.caid, rl.provid, rl.srvid, rl.chid, rl.ratelimitecm, rl.ratelimittime, rl.srvidholdtime);
	}
	else // nothing found: apply general reader limits
	{
		rl.ratelimitecm = reader->ratelimitecm;
		rl.ratelimittime = reader->ratelimittime;
		rl.srvidholdtime = reader->srvidholdtime;
		rl.caid = er->caid;
		rl.provid = er->prid;
		rl.chid = er->chid;
		rl.srvid = er->srvid;
		cs_log_dbg(D_CLIENT, "ratelimiter apply readerdefault for CAID: %04X PROVID: %06X SRVID: %04X CHID: %04X maxecms: %d cycle: %d ms srvidhold: %d ms",
					rl.caid, rl.provid, rl.srvid, rl.chid, rl.ratelimitecm, rl.ratelimittime, rl.srvidholdtime);
	}

	// Below this line: rate limit functionality.
	// No cooldown set
	if(!reader->cooldown[0])
	{
		cs_log_dbg(D_CLIENT, "ratelimiter find a slot for srvid %04X on reader %s", er->srvid, reader->label);
		foundspace = ecm_ratelimit_findspace(reader, er, rl, reader_mode);
		if(foundspace < 0)
		{
			if(reader_mode)
			{
				if(foundspace != -2)
				{
					cs_log_dbg(D_CLIENT, "ratelimiter no free slot for srvid %04X on reader %s -> dropping!", er->srvid, reader->label);
					write_ecm_answer(reader, er, E_NOTFOUND, E2_RATELIMIT, NULL, "Ratelimiter: no slots free!", 0, NULL);
				}
			}

			return ERROR; // not even trowing an error... obvious reason ;)
		}
		else // we are within ecmratelimits
		{
			if(reader_mode)
			{
				// Register new slot
				//reader->rlecmh[foundspace].srvid=er->srvid; // register srvid
				reader->rlecmh[foundspace] = rl; // register this srvid ratelimit params
				cs_ftime(&reader->rlecmh[foundspace].last); // register request time
				memcpy(reader->rlecmh[foundspace].ecmd5, er->ecmd5, CS_ECMSTORESIZE); // register ecmhash
				reader->rlecmh[foundspace].kindecm = er->ecm[0]; // register kind of ecm
			}

			return OK;
		}
	}

	// Below this line: rate limit functionality with cooldown option.

	// Cooldown state cycle:
	// state = 0: Cooldown setup phase. No rate limit set.
	//  If number of ecm request exceed reader->ratelimitecm, cooldownstate goes to 2.
	// state = 2: Cooldown delay phase. No rate limit set.
	//  If number of ecm request still exceed reader->ratelimitecm at end of cooldown delay phase,
	//      cooldownstate goes to 1 (rate limit phase).
	//  Else return back to setup phase (state 0).
	// state = 1: Cooldown ratelimit phase. Rate limit set.
	//  If cooldowntime reader->cooldown[1] is elapsed, return to cooldown setup phase (state 0).

	cs_ftime(&now);
	int32_t gone = comp_timeb(&now, &reader->cooldowntime);
	if(reader->cooldownstate == 1) // Cooldown in ratelimit phase
	{
		if(gone <= reader->cooldown[1] * 1000) // check if cooldowntime is elapsed
			{ maxslots = reader->ratelimitecm; } // use user defined ratelimitecm
		else // Cooldown time is elapsed
		{
			reader->cooldownstate = 0; // set cooldown setup phase
			reader->cooldowntime.time = -1; // reset cooldowntime
			maxslots = MAXECMRATELIMIT; //use oscam defined max slots
			cs_log("Reader: %s ratelimiter returning to setup phase cooling down period of %d seconds is done!", reader->label, reader->cooldown[1]);
		}
	} // if cooldownstate == 1

	if(reader->cooldownstate == 2 && gone > reader->cooldown[0] * 1000)
	{
		// Need to check if the otherslots are not exceeding the ratelimit at the moment that
		// cooldown[0] time was exceeded!
		// time_t actualtime = reader->cooldowntime + reader->cooldown[0];
		maxslots = 0; // maxslots is used as counter
		for(h = 0; h < MAXECMRATELIMIT; h++)
		{
			if(reader->rlecmh[h].last.time == -1) { continue; } // skip empty slots
			// how many active slots are registered at end of cooldown delay period

			gone = comp_timeb(&now, &reader->rlecmh[h].last);
			if(gone <= (reader->ratelimittime + reader->srvidholdtime))
			{
				maxslots++;
				if(maxslots >= reader->ratelimitecm) { break; } // Need to go cooling down phase
			}
		}

		if(maxslots < reader->ratelimitecm)
		{
			reader->cooldownstate = 0; // set cooldown setup phase
			reader->cooldowntime.time = -1; // reset cooldowntime
			maxslots = MAXECMRATELIMIT; // maxslots is maxslots again
			cs_log("Reader: %s ratelimiter returning to setup phase after %d seconds cooldowndelay!", reader->label, reader->cooldown[0]);
		} else
		{
			reader->cooldownstate = 1; // Entering ratelimit for cooldown ratelimitseconds
			cs_ftime(&reader->cooldowntime); // set time to enforce ecmratelimit for defined cooldowntime
			maxslots = reader->ratelimitecm; // maxslots is maxslots again
			sort_ecmrl(reader); // keep youngest ecm requests in list + housekeeping
			cs_log("Reader: %s ratelimiter starting cooling down period of %d seconds!", reader->label, reader->cooldown[1]);
		}
	} // if cooldownstate == 2

	cs_log_dbg(D_CLIENT, "ratelimiter cooldownphase %d find a slot for srvid %04X on reader %s", reader->cooldownstate, er->srvid, reader->label);

	foundspace = ecm_ratelimit_findspace(reader, er, rl, reader_mode);

	if(foundspace < 0)
	{
		if(reader_mode)
		{
			if(foundspace != -2)
			{
				cs_log_dbg(D_CLIENT, "ratelimiter cooldownphase %d no free slot for srvid %04X on reader %s -> dropping!", reader->cooldownstate, er->srvid, reader->label);
				write_ecm_answer(reader, er, E_NOTFOUND, E2_RATELIMIT, NULL, "Ratelimiter: cooldown no slots free!", 0, NULL);
			}
		}

		return ERROR; // not even trowing an error... obvious reason ;)
	}
	else // we are within ecmratelimits
	{
		if(reader_mode)
		{
			// Register new slot
			//reader->rlecmh[foundspace].srvid=er->srvid; // register srvid
			reader->rlecmh[foundspace] = rl; // register this srvid ratelimit params
			cs_ftime(&reader->rlecmh[foundspace].last); // register request time
			memcpy(reader->rlecmh[foundspace].ecmd5, er->ecmd5, CS_ECMSTORESIZE);// register ecmhash
			reader->rlecmh[foundspace].kindecm = er->ecm[0]; // register kind of ecm
		}
	}

	if(reader->cooldownstate == 0 && foundspace >= reader->ratelimitecm)
	{
		if(!reader_mode) // No actual ecm request, just check
		{
			return OK;
		}

		cs_log("Reader: %s ratelimiter cooldown detected overrun ecmratelimit of %d during setup phase!",
				reader->label, (foundspace - reader->ratelimitecm + 1));
		reader->cooldownstate = 2; // Entering cooldowndelay phase
		cs_ftime(&reader->cooldowntime); // Set cooldowntime to calculate delay
		cs_log_dbg(D_CLIENT, "ratelimiter cooldowndelaying %d seconds", reader->cooldown[0]);
	}

	// Cooldown state housekeeping is done. There is a slot available.
	if(reader_mode)
	{
		// Register new slot
		//reader->rlecmh[foundspace].srvid=er->srvid; // register srvid
		reader->rlecmh[foundspace] = rl; // register this srvid ratelimit params
		cs_ftime(&reader->rlecmh[foundspace].last); // register request time
		memcpy(reader->rlecmh[foundspace].ecmd5, er->ecmd5, CS_ECMSTORESIZE);// register ecmhash
		reader->rlecmh[foundspace].kindecm = er->ecm[0]; // register kind of ecm
	}

	return OK;
}

const struct s_cardsystem *get_cardsystem_by_caid(uint16_t caid)
{
	int32_t i, j;
	for(i = 0; cardsystems[i]; i++)
	{
		const struct s_cardsystem *csystem = cardsystems[i];
		for(j = 0; csystem->caids[j]; j++)
		{
			uint16_t cs_caid = csystem->caids[j];
			if(!cs_caid)
				{ continue; }
			if(cs_caid == caid || cs_caid == caid >> 8)
				{ return csystem; }
		}
	}
	return NULL;
}

struct s_reader *get_reader_by_label(char *lbl)
{
	struct s_reader *rdr;
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		if(streq(lbl, rdr->label))
			{ break; }
	}
	return rdr;
}

const char *reader_get_type_desc(struct s_reader *rdr, int32_t extended)
{
	const char *desc = "unknown";
	if(rdr->crdr && rdr->crdr->desc)
		{ return rdr->crdr->desc; }
	if(is_network_reader(rdr) || rdr->typ == R_SERIAL)
	{
		if(rdr->ph.desc)
			{ desc = rdr->ph.desc; }
	}
	if(rdr->typ == R_NEWCAMD && rdr->ncd_proto == NCD_524)
		{ desc = "newcamd524"; }
	else if(rdr->typ == R_CCCAM)
	{
		desc = "cccam";
		if(extended && cccam_client_extended_mode(rdr->client)) desc = "cccam_ext";
		if(cccam_client_multics_mode(rdr->client)) desc = "cccam_mcs";
	}
	return desc;
}

bool hexserialset(struct s_reader *rdr)
{
	int i;
	if(!rdr)
		{ return false; }
	for(i = 0; i < 8; i++)
	{
		if(rdr->hexserial[i])
			{ return true; }
	}
	return false;
}

void hexserial_to_newcamd(uint8_t *source, uint8_t *dest, uint16_t caid)
{
	if(caid_is_bulcrypt(caid) || caid_is_streamguard(caid) || caid_is_tongfang(caid) || caid_is_dvn(caid))
	{
		dest[0] = 0x00;
		dest[1] = 0x00;
		memcpy(dest + 2, source, 4);
	}
	else if(caid_is_irdeto(caid) || caid_is_betacrypt(caid))
	{
		// only 4 Bytes Hexserial for newcamd clients (Hex Base + Hex Serial)
		// first 2 Byte always 00
		dest[0] = 0x00; //serial only 4 bytes
		dest[1] = 0x00; //serial only 4 bytes
		// 1 Byte Hex Base (see reader-irdeto.c how this is stored in "source")
		dest[2] = source[3];
		// 3 Bytes Hex Serial (see reader-irdeto.c how this is stored in "source")
		dest[3] = source[0];
		dest[4] = source[1];
		dest[5] = source[2];
	}
	else if(caid_is_viaccess(caid) || caid_is_cryptoworks(caid))
	{
		dest[0] = 0x00;
		memcpy(dest + 1, source, 5);
	}
	else
	{
		memcpy(dest, source, 6);
	}
}

void newcamd_to_hexserial(uint8_t *source, uint8_t *dest, uint16_t caid)
{
	if(caid_is_bulcrypt(caid) || caid_is_streamguard(caid) || caid_is_tongfang(caid) || caid_is_dvn(caid))
	{
		memcpy(dest, source + 2, 4);
		dest[4] = 0x00;
		dest[5] = 0x00;
	}
	else if(caid_is_irdeto(caid) || caid_is_betacrypt(caid))
	{
		memcpy(dest, source + 3, 3);
		dest[3] = source[2];
		dest[4] = 0;
		dest[5] = 0;
	}
	else if(caid_is_viaccess(caid) || caid_is_cryptoworks(caid))
	{
		memcpy(dest, source + 1, 5);
		dest[5] = 0;
	}
	else
	{
		memcpy(dest, source, 6);
	}
}

/**
 * add or find one entitlement item to entitlements of reader
 * use add = 0 for find only, or add > 0 to find and add if not found
 **/
S_ENTITLEMENT *cs_add_entitlement(struct s_reader *rdr, uint16_t caid, uint32_t provid, uint64_t id, uint32_t class, time_t start, time_t end, uint8_t type, uint8_t add)
{
	if(!rdr->ll_entitlements)
	{
		rdr->ll_entitlements = ll_create("ll_entitlements");
	}

	S_ENTITLEMENT *item = NULL;
	LL_ITER it;

	it = ll_iter_create(rdr->ll_entitlements);
	while((item = ll_iter_next(&it)) != NULL)
	{
		if((caid && item->caid != caid) || (provid && item->provid != provid) ||
			(id && item->id != id) || (class && item->class != class) ||
			(start && ((!add && item->start < start) || (add && item->start !=start))) ||
			(end && ((!add && item->end < end) || (add && item->end != end))) ||
			(type && item->type != type))
		{
			continue; // no match, try next!
		}
		break; // match found!
	}

	if(add && item == NULL)
	{
		if(cs_malloc(&item, sizeof(S_ENTITLEMENT)))
		{
			// fill item
			item->caid = caid;
			item->provid = provid;
			item->id = id;
			item->class = class;
			item->start = start;
			item->end = end;
			item->type = type;

			// add item
			ll_append(rdr->ll_entitlements, item);
			// cs_log_dbg(D_TRACE, "entitlement: Add caid %4X id %4X %s - %s ", item->caid, item->id, item->start, item->end);
		}
		else
		{
			cs_log("ERROR: Can't allocate entitlement to reader!");
		}
	}

	return item;
}

/**
 * clears entitlements of reader.
 **/
void cs_clear_entitlement(struct s_reader *rdr)
{
	if(!rdr->ll_entitlements)
		{ return; }

	ll_clear_data(rdr->ll_entitlements);
}


void casc_check_dcw(struct s_reader *reader, int32_t idx, int32_t rc, uint8_t *cw)
{
	int32_t i, pending = 0;
	time_t t = time(NULL);
	ECM_REQUEST *ecm;
	struct s_client *cl = reader->client;

	if(!check_client(cl)) { return; }

	for(i = 0; i < cfg.max_pending; i++)
	{
		ecm = &cl->ecmtask[i];
		if((ecm->rc >= E_NOCARD) && ecm->caid == cl->ecmtask[idx].caid && (!memcmp(ecm->ecmd5, cl->ecmtask[idx].ecmd5, CS_ECMSTORESIZE)))
		{
			if(rc == 2) // E_INVALID from camd35 CMD08
			{
				write_ecm_answer(reader, ecm, E_INVALID, 0, cw, NULL, 0, NULL);
			}
			else if(rc)
			{
#ifdef CS_CACHEEX_AIO
				if(rc == 0x86) // lg-flagged rc
				{
					ecm->localgenerated = 1;
				}
#endif
				write_ecm_answer(reader, ecm, E_FOUND, 0, cw, NULL, 0, NULL);
			}
			else
			{
				write_ecm_answer(reader, ecm, E_NOTFOUND, 0 , NULL, NULL, 0, NULL);
			}
			ecm->idx = 0;
			ecm->rc = E_FOUND;
		}

		if(ecm->rc >= E_NOCARD && (t - (uint32_t)ecm->tps.time > ((cfg.ctimeout + 500) / 1000) + 1)) // drop timeouts
		{
			ecm->rc = E_FOUND;
		}

		if(ecm->rc >= E_NOCARD)
			{ pending++; }
	}
	cl->pending = pending;
}

int32_t hostResolve(struct s_reader *rdr)
{
	struct s_client *cl = rdr->client;

	if(!cl) { return 0; }

	IN_ADDR_T last_ip;
	IP_ASSIGN(last_ip, cl->ip);
	cs_resolve(rdr->device, &cl->ip, &cl->udp_sa, &cl->udp_sa_len);
	IP_ASSIGN(SIN_GET_ADDR(cl->udp_sa), cl->ip);

	if(!IP_EQUAL(cl->ip, last_ip))
	{
		cs_log("%s: resolved ip=%s", rdr->device, cs_inet_ntoa(cl->ip));
	}

	return IP_ISSET(cl->ip);
}

void clear_block_delay(struct s_reader *rdr)
{
	rdr->tcp_block_delay = 0;
	cs_ftime(&rdr->tcp_block_connect_till);
}

void block_connect(struct s_reader *rdr)
{
	if(!rdr->tcp_block_delay)
		{ rdr->tcp_block_delay = 100; } // starting blocking time, 100ms

	cs_ftime(&rdr->tcp_block_connect_till);
	add_ms_to_timeb(&rdr->tcp_block_connect_till, rdr->tcp_block_delay);
	rdr->tcp_block_delay *= 4; // increment timeouts

	if(rdr->tcp_block_delay >= rdr->tcp_reconnect_delay)
		{ rdr->tcp_block_delay = rdr->tcp_reconnect_delay; }

	rdr_log_dbg(rdr, D_TRACE, "tcp connect blocking delay set to %d", rdr->tcp_block_delay);
}

int32_t is_connect_blocked(struct s_reader *rdr)
{
	struct timeb cur_time;
	cs_ftime(&cur_time);
	int32_t diff = comp_timeb(&cur_time, &rdr->tcp_block_connect_till);
	int32_t blocked = rdr->tcp_block_delay && diff < 0;

	if(blocked)
		rdr_log_dbg(rdr, D_TRACE, "connection blocked, retrying in %d ms", -diff);

	return blocked;
}

int32_t network_tcp_connection_open(struct s_reader *rdr)
{
	if(!rdr) { return -1; }
	struct s_client *client = rdr->client;
	struct SOCKADDR loc_sa;

	memset((char *)&client->udp_sa, 0, sizeof(client->udp_sa));

	IN_ADDR_T last_ip;
	IP_ASSIGN(last_ip, client->ip);
	if(!hostResolve(rdr))
		{ return -1; }

	if(!IP_EQUAL(last_ip, client->ip)) // clean blocking delay on ip change:
		{ clear_block_delay(rdr); }

	if(is_connect_blocked(rdr)) // inside of blocking delay, do not connect!
	{
		return -1;
	}

	if(client->reader->r_port <= 0)
	{
		rdr_log(client->reader, "invalid port %d for server %s", client->reader->r_port, client->reader->device);
		return -1;
	}

	client->is_udp = (rdr->typ == R_CAMD35);

	rdr_log(rdr, "connecting to %s:%d", rdr->device, rdr->r_port);

	if(client->udp_fd)
		{ rdr_log(rdr, "WARNING: client->udp_fd was not 0"); }

	int s_domain = PF_INET;
	int s_family = AF_INET;
#ifdef IPV6SUPPORT
	if(!IN6_IS_ADDR_V4MAPPED(&rdr->client->ip) && !IN6_IS_ADDR_V4COMPAT(&rdr->client->ip))
	{
		s_domain = PF_INET6;
		s_family = AF_INET6;
	}
#endif
	int s_type = client->is_udp ? SOCK_DGRAM : SOCK_STREAM;
	int s_proto = client->is_udp ? IPPROTO_UDP : IPPROTO_TCP;

	if((client->udp_fd = socket(s_domain, s_type, s_proto)) < 0)
	{
		rdr_log(rdr, "Socket creation failed (errno=%d %s)", errno, strerror(errno));
		client->udp_fd = 0;
		block_connect(rdr);
		return -1;
	}

	set_socket_priority(client->udp_fd, cfg.netprio);

	int32_t keep_alive = 1;
	setsockopt(client->udp_fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&keep_alive, sizeof(keep_alive));

	int32_t flag = 1;
	setsockopt(client->udp_fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flag, sizeof(flag));

	if(setsockopt(client->udp_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flag, sizeof(flag)) < 0)
	{
		rdr_log(rdr, "setsockopt failed (errno=%d: %s)", errno, strerror(errno));
		client->udp_fd = 0;
		block_connect(rdr);
		return -1;
	}

	set_so_reuseport(client->udp_fd);

	memset((char *)&loc_sa, 0, sizeof(loc_sa));
	SIN_GET_FAMILY(loc_sa) = s_family;

	if(IP_ISSET(cfg.srvip))
		{ IP_ASSIGN(SIN_GET_ADDR(loc_sa), cfg.srvip); }
	else
		{ SIN_GET_ADDR(loc_sa) = ADDR_ANY; }

	if(client->reader->l_port)
		{ SIN_GET_PORT(loc_sa) = htons(client->reader->l_port); }

	if(client->is_udp && bind(client->udp_fd, (struct sockaddr *)&loc_sa, sizeof(loc_sa)) < 0)
	{
		rdr_log(rdr, "bind failed (errno=%d %s)", errno, strerror(errno));
		close(client->udp_fd);
		client->udp_fd = 0;
		block_connect(rdr);
		return -1;
	}

#ifdef IPV6SUPPORT
	if(IN6_IS_ADDR_V4MAPPED(&rdr->client->ip) || IN6_IS_ADDR_V4COMPAT(&rdr->client->ip))
	{
		((struct sockaddr_in *)(&client->udp_sa))->sin_family = AF_INET;
		((struct sockaddr_in *)(&client->udp_sa))->sin_port = htons((uint16_t)client->reader->r_port);
	}
	else
	{
		((struct sockaddr_in6 *)(&client->udp_sa))->sin6_family = AF_INET6;
		((struct sockaddr_in6 *)(&client->udp_sa))->sin6_port = htons((uint16_t)client->reader->r_port);
	}
#else
	client->udp_sa.sin_family = AF_INET;
	client->udp_sa.sin_port = htons((uint16_t)client->reader->r_port);
#endif

	rdr_log_dbg(rdr, D_TRACE, "socket open fd=%d", client->udp_fd);

	if(client->is_udp)
	{
		rdr->tcp_connected = 1;
		return client->udp_fd;
	}

	set_nonblock(client->udp_fd, true);

	int32_t res = connect(client->udp_fd, (struct sockaddr *)&client->udp_sa, client->udp_sa_len);
	if(res == -1)
	{
		int32_t r = -1;
		if(errno == EINPROGRESS || errno == EALREADY)
		{
			struct pollfd pfd;
			pfd.fd = client->udp_fd;
			pfd.events = POLLOUT;
			int32_t rc = poll(&pfd, 1, 3000);
			if(rc > 0)
			{
				uint32_t l = sizeof(r);
				if(getsockopt(client->udp_fd, SOL_SOCKET, SO_ERROR, &r, (socklen_t *)&l) != 0)
					{ r = -1; }
				else
					{ errno = r; }
			}
			else
			{
				errno = ETIMEDOUT;
			}
		}
		if(r != 0)
		{
			rdr_log(rdr, "connect failed: %s", strerror(errno));
			block_connect(rdr); // connect has failed. Block connect for a while
			close(client->udp_fd);
			client->udp_fd = 0;
			return -1;
		}
	}

	set_nonblock(client->udp_fd, false); // restore blocking mode

	setTCPTimeouts(client->udp_fd);
	clear_block_delay(rdr);
	client->last = client->login = time((time_t *)0);
	client->last_caid = NO_CAID_VALUE;
	client->last_provid = NO_PROVID_VALUE;
	client->last_srvid = NO_SRVID_VALUE;
	client->pfd = client->udp_fd;
	rdr->tcp_connected = 1;
	rdr_log_dbg(rdr, D_TRACE, "connect successful fd=%d", client->udp_fd);
	return client->udp_fd;
}

void network_tcp_connection_close(struct s_reader *reader, char *reason)
{
	if(!reader)
	{
		// only proxy reader should call this, client connections are closed on thread cleanup
		cs_log("WARNING: invalid client");
		cs_disconnect_client(cur_client());
		return;
	}

	struct s_client *cl = reader->client;
	if(!cl) { return; }
	int32_t fd = cl->udp_fd;

	int32_t i;

	if(fd)
	{
		rdr_log(reader, "disconnected: reason %s", reason ? reason : "undef");
		close(fd);

		cl->udp_fd = 0;
		cl->pfd = 0;
	}

	reader->tcp_connected = 0;
	reader->card_status = UNKNOWN;
	cl->logout = time((time_t *)0);

	if(cl->ecmtask)
	{
		for(i = 0; i < cfg.max_pending; i++)
		{
			cl->ecmtask[i].idx = 0;
			cl->ecmtask[i].rc = E_FOUND;
		}
	}
	// newcamd message ids are stored as a reference in ecmtask[].idx
	// so we need to reset them aswell
	if(reader->typ == R_NEWCAMD)
		{ cl->ncd_msgid = 0; }
}

int32_t casc_process_ecm(struct s_reader *reader, ECM_REQUEST *er)
{
	int32_t rc, n, i, sflag, pending = 0;
	time_t t;//, tls;
	struct s_client *cl = reader->client;

	if(!cl || !cl->ecmtask)
	{
		rdr_log(reader, "WARNING: ecmtask not available");
		return -1;
	}

	t = time((time_t *)0);
	ECM_REQUEST *ecm;
	for(i = 0; i < cfg.max_pending; i++)
	{
		ecm = &cl->ecmtask[i];
		if((ecm->rc >= E_NOCARD) && (t - (uint32_t)ecm->tps.time > ((cfg.ctimeout + 500) / 1000) + 1)) // drop timeouts
		{
			ecm->rc = E_FOUND;
		}
	}

	for(n = -1, i = 0, sflag = 1; i < cfg.max_pending; i++)
	{
		ecm = &cl->ecmtask[i];
		if(n < 0 && (ecm->rc < E_NOCARD)) // free slot found
			{ n = i; }

		// ecm already pending
		// ...this level at least
		if((ecm->rc >= E_NOCARD) && er->caid == ecm->caid && (!memcmp(er->ecmd5, ecm->ecmd5, CS_ECMSTORESIZE)))
			{ sflag = 0; }

		if(ecm->rc >= E_NOCARD)
			{ pending++; }
	}
	cl->pending = pending;

	if(n < 0)
	{
		rdr_log(reader, "WARNING: reader ecm pending table overflow !!");
		return (-2);
	}

	memcpy(&cl->ecmtask[n], er, sizeof(ECM_REQUEST));
	cl->ecmtask[n].matching_rdr = NULL; // This avoids double free of matching_rdr!
#ifdef CS_CACHEEX
	cl->ecmtask[n].csp_lastnodes = NULL; // This avoids double free of csp_lastnodes!
#endif
	cl->ecmtask[n].parent = er;

	if(reader->typ == R_NEWCAMD)
		{ cl->ecmtask[n].idx = (cl->ncd_msgid == 0) ? 2 : cl->ncd_msgid + 1; }
	else
	{
		if(!cl->idx)
			{ cl->idx = 1; }
		cl->ecmtask[n].idx = cl->idx++;
	}

	cl->ecmtask[n].rc = E_NOCARD;
	cs_log_dbg(D_TRACE, "---- ecm_task %d, idx %d, sflag=%d", n, cl->ecmtask[n].idx, sflag);

	cs_log_dump_dbg(D_ATR, er->ecm, er->ecmlen, "casc ecm (%s):", (reader) ? reader->label : "n/a");
	rc = 0;

	if(sflag)
	{
		rc = reader->ph.c_send_ecm(cl, &cl->ecmtask[n]);
		if(rc != 0)
		{
			casc_check_dcw(reader, n, 0, cl->ecmtask[n].cw); // simulate "not found"
		}
		else
			{ cl->last_idx = cl->ecmtask[n].idx; }
		reader->last_s = t; // used for inactive_timeout and reconnect_timeout in TCP reader
	}

	if(cl->idx > 0x1ffe) { cl->idx = 1; }

	return (rc);
}

void reader_get_ecm(struct s_reader *reader, ECM_REQUEST *er)
{
	if(!reader) { return; }
	struct s_client *cl = reader->client;
	if(!check_client(cl)) { return; }

	if(!chk_bcaid(er, &reader->ctab))
	{
		rdr_log_dbg(reader, D_READER, "caid %04X filtered", er->caid);
		write_ecm_answer(reader, er, E_NOTFOUND, E2_CAID, NULL, NULL, 0, NULL);
		return;
	}

	// CHECK if ecm already sent to reader
	struct s_ecm_answer *ea_er = get_ecm_answer(reader, er);
	if(!ea_er) { return; }

	struct s_ecm_answer *ea = NULL, *ea_prev = NULL;
	struct ecm_request_t *ecm;
	time_t timeout;

	cs_readlock(__func__, &ecmcache_lock);

		for(ecm = ecmcwcache; ecm; ecm = ecm->next)
		{
			timeout = time(NULL) - ((cfg.ctimeout+500)/1000+1);
			if(ecm->tps.time <= timeout)
				{ break; }

			if(!ecm->matching_rdr || ecm == er || ecm->rc == E_99) { continue; }

			// match same ecm
			if(er->caid == ecm->caid && !memcmp(er->ecmd5, ecm->ecmd5, CS_ECMSTORESIZE))
			{
				//check if ask this reader
				ea = get_ecm_answer(reader, ecm);
				if(ea && !ea->is_pending && (ea->status & REQUEST_SENT) && ea->rc != E_TIMEOUT && ea->rcEx != E2_RATELIMIT) { break; }
				ea = NULL;
			}
		}

		cs_readunlock(__func__, &ecmcache_lock);

		if(ea) // found ea in cached ecm, asking for this reader
		{
			ea_er->is_pending = true;

			cs_readlock(__func__, &ea->ecmanswer_lock);
			if(ea->rc < E_99)
			{
				cs_readunlock(__func__, &ea->ecmanswer_lock);
				cs_log_dbg(D_LB, "{client %s, caid %04X, prid %06X, srvid %04X} [reader_get_ecm] ecm already sent to reader %s (%s)", (check_client(er->client) ? er->client->account->usr : "-"), er->caid, er->prid, er->srvid, reader ? reader->label : "-", ea->rc==E_FOUND?"OK":"NOK");

				//e.g. we cannot send timeout, because "ea_temp->er->client" could wait/ask other readers! Simply set not_found if different from E_FOUND!
				write_ecm_answer(reader, er, (ea->rc==E_FOUND? E_FOUND : E_NOTFOUND), ea->rcEx, ea->cw, NULL, ea->tier, &ea->cw_ex);
				return;
			}
			else
			{
				ea_prev = ea->pending;
				ea->pending = ea_er;
				ea->pending->pending_next = ea_prev;
				cs_log_dbg(D_LB, "{client %s, caid %04X, prid %06X, srvid %04X} [reader_get_ecm] ecm already sent to reader %s... set as pending", (check_client(er->client) ? er->client->account->usr : "-"), er->caid, er->prid, er->srvid, reader ? reader->label : "-");
			}
			cs_readunlock(__func__, &ea->ecmanswer_lock);
			return;
		}

	lb_update_last(ea_er, reader);

	if(ecm_ratelimit_check(reader, er, 1) != OK)
	{
		rdr_log_dbg(reader, D_READER, "ratelimiter has no space left -> skip!");
		return;
	}

	if(is_cascading_reader(reader)) // forward request to proxy reader
	{
		cl->last_srvid = er->srvid;
		cl->last_caid = er->caid;
		cl->last_provid = er->prid;
		casc_process_ecm(reader, er);
		cl->lastecm = time((time_t *)0);
		return;
	}

	cardreader_process_ecm(reader, cl, er); // forward request to physical reader
}

void reader_do_card_info(struct s_reader *reader)
{
	cardreader_get_card_info(reader);
	if(reader->ph.c_card_info)
		{ reader->ph.c_card_info(); }
}

void reader_do_idle(struct s_reader *reader)
{
	if(reader->ph.c_idle)
		{ reader->ph.c_idle(); }
	else if (reader->tcp_ito > 0)
	{
		time_t now;
		int32_t time_diff;
		time(&now);
		time_diff = llabs(now - reader->last_s);
		if(time_diff > reader->tcp_ito)
		{
			struct s_client *cl = reader->client;
			if(check_client(cl) && reader->tcp_connected && reader->ph.type == MOD_CONN_TCP)
			{
				rdr_log_dbg(reader, D_READER, "inactive_timeout, close connection (fd=%d)", cl->pfd);
				network_tcp_connection_close(reader, "inactivity");
			}
			else
				{ reader->last_s = now; }
		}
	}
}

int32_t reader_init(struct s_reader *reader)
{
	struct s_client *client = reader->client;

	if(is_cascading_reader(reader))
	{
		client->typ = 'p';
		client->port = reader->r_port;
		set_null_ip(&client->ip);

		if(!(reader->ph.c_init))
		{
			rdr_log(reader, "FATAL: protocol not supporting cascading");
			return 0;
		}

		if(reader->ph.c_init(client))
		{
			//proxy reader start failed
			return 0;
		}

		if(client->ecmtask)
		{
			add_garbage(client->ecmtask);
			client->ecmtask = NULL;
		}

		if(!cs_malloc(&client->ecmtask, cfg.max_pending * sizeof(ECM_REQUEST)))
			{ return 0; }

		rdr_log(reader, "proxy initialized, server %s:%d", reader->device, reader->r_port);
	}
	else
	{
		if(!cardreader_init(reader))
			{ return 0; }
	}

	ll_destroy_data(&reader->emmstat);

	client->login = time((time_t *)0);
	client->init_done = 1;

	return 1;
}

#if !defined(WITH_CARDREADER) && (defined(WITH_STAPI) || defined(WITH_STAPI5))
/* Dummy function stub for stapi compiles without cardreader as libstapi needs it. */
int32_t ATR_InitFromArray(ATR *atr, const uint8_t atr_buffer[ATR_MAX_SIZE], uint32_t length)
{
	(void)atr;
	(void)atr_buffer;
	(void)length;
	return 0;
}
#endif

void cs_card_info(void)
{
	struct s_client *cl;
	for(cl = first_client->next; cl ; cl = cl->next)
	{
		if(cl->typ == 'r' && cl->reader)
			{ add_job(cl, ACTION_READER_CARDINFO, NULL, 0); }
	}
}


/* Adds a reader to the list of active readers so that it can serve ecms. */
static void add_reader_to_active(struct s_reader *rdr)
{
	struct s_reader *rdr2, *rdr_prv = NULL, *rdr_tmp = NULL;
	int8_t at_first = 1;

	if(rdr->next)
		{ remove_reader_from_active(rdr); }

	cs_writelock(__func__, &readerlist_lock);
	cs_writelock(__func__, &clientlist_lock);

	// search configured position:
	LL_ITER it = ll_iter_create(configured_readers);
	while((rdr2 = ll_iter_next(&it)))
	{
		if(rdr2 == rdr)
			{ break; }
		if(rdr2->client && rdr2->enable)
		{
			rdr_prv = rdr2;
			at_first = 0;
		}
	}

	// insert at configured position:
	if(first_active_reader)
	{
		if(at_first)
		{
			rdr->next = first_active_reader;
			first_active_reader = rdr;

			// resort client list:
			struct s_client *prev, *cl;

			for(prev = first_client, cl = first_client->next;
					prev->next != NULL; prev = prev->next, cl = cl->next)
			{
				if(rdr->client == cl)
					{ break; }
			}

			if(cl && rdr->client == cl)
			{
				prev->next = cl->next; // remove client from list
				cl->next = first_client->next;
				first_client->next = cl;
			}
		}
		else
		{
			for(rdr2 = first_active_reader; rdr2->next && rdr2 != rdr_prv ; rdr2 = rdr2->next) { ; } // search last element

			rdr_prv = rdr2;
			rdr_tmp = rdr2->next;
			rdr2->next = rdr;
			rdr->next = rdr_tmp;

			// resort client list:
			struct s_client *prev, *cl;

			for(prev = first_client, cl = first_client->next;
					prev->next != NULL; prev = prev->next, cl = cl->next)
			{
				if(rdr->client == cl)
					{ break; }
			}

			if(cl && rdr->client == cl)
			{
				prev->next = cl->next; // remove client from list
				cl->next = rdr_prv->client->next;
				rdr_prv->client->next = cl;
			}
		}
	}
	else
	{
		first_active_reader = rdr;
	}
	rdr->active = 1;
	cs_writeunlock(__func__, &clientlist_lock);
	cs_writeunlock(__func__, &readerlist_lock);
}

/* Removes a reader from the list of active readers so that no ecms can be requested anymore. */
void remove_reader_from_active(struct s_reader *rdr)
{
	struct s_reader *rdr2, *prv = NULL;
	//rdr_log(rdr, "CHECK: REMOVE READER FROM ACTIVE");
	cs_writelock(__func__, &readerlist_lock);
	for(rdr2 = first_active_reader; rdr2 ; prv = rdr2, rdr2 = rdr2->next)
	{
		if(rdr2 == rdr)
		{
			if(prv) { prv->next = rdr2->next; }
			else { first_active_reader = rdr2->next; }
			break;
		}
	}
	rdr->next = NULL;
	rdr->active = 0;
	cs_writeunlock(__func__, &readerlist_lock);
}

/* Starts or restarts a cardreader without locking. If restart=1, the existing thread is killed before restarting,
   if restart=0 the cardreader is only started. */
static int32_t restart_cardreader_int(struct s_reader *rdr, int32_t restart)
{
	struct s_client *cl = rdr->client;
	if(restart)
	{
		uint16_t waitme = 1500;
		remove_reader_from_active(rdr); // remove from list
		kill_thread(cl); // kill old thread

		// wait a bit if socket not closed and is_valid_client, othervise safe for reload?
		do
		{
			if (!is_valid_client(cl))
			{
				// 100 mS I think is enought for freeing garbage
				cs_sleepms(100);
				break;
			}
			else
			{
				// If we quick disable+enable a reader (webif), remove_reader_from_active is called from
				// cleanup. this could happen AFTER reader is restarted, so oscam crashes or reader is hidden
				// rdr_log(rdr, "CHECK: WAITING FOR CLEANUP");
				cs_sleepms(500); // we have to wait a bit so free_client is ended and socket closed too!
				waitme -= 500;
			}
		} while(waitme || cl->pfd);
	}

	rdr->client = NULL;
	rdr->tcp_connected = 0;
	rdr->card_status = UNKNOWN;
	rdr->tcp_block_delay = 100;
	cs_ftime(&rdr->tcp_block_connect_till);

	if(rdr->device[0] && is_cascading_reader(rdr))
	{
		if(!rdr->ph.num)
		{
			rdr_log(rdr, "Protocol Support missing. (typ=%d)", rdr->typ);
			return 0;
		}
	}

	if(!rdr->enable)
		{ return 0; }

	if(rdr->device[0])
	{
		if(restart)
		{
			rdr_log(rdr, "Restarting reader");
		}
		cl = create_client(first_client->ip);
		if(cl == NULL)
			{ return 0; }
		cl->reader  = rdr;
		rdr_log(rdr, "creating thread for device %s", rdr->device);

		cl->sidtabs.ok = rdr->sidtabs.ok;
		cl->sidtabs.no = rdr->sidtabs.no;
		cl->lb_sidtabs.ok = rdr->lb_sidtabs.ok;
		cl->lb_sidtabs.no = rdr->lb_sidtabs.no;
		cl->grp = rdr->grp;

		rdr->client = cl;

		cl->typ = 'r';

		add_job(cl, ACTION_READER_INIT, NULL, 0);
		add_reader_to_active(rdr);

		return 1;
	}
	return 0;
}

/* Starts or restarts a cardreader with locking. If restart=1, the existing thread is killed before restarting,
   if restart=0 the cardreader is only started. */
int32_t restart_cardreader(struct s_reader *rdr, int32_t restart)
{
	cs_writelock(__func__, &system_lock);
	int32_t result = restart_cardreader_int(rdr, restart);
	cs_writeunlock(__func__, &system_lock);
	return result;
}

void init_cardreader(void)
{
	cs_log_dbg(D_TRACE, "cardreader: Initializing");
	cs_writelock(__func__, &system_lock);
	struct s_reader *rdr;

	cardreader_init_locks();
	LL_ITER itr = ll_iter_create(configured_readers);
	while((rdr = ll_iter_next(&itr)))
	{
		if(rdr->enable)
		{
			restart_cardreader_int(rdr, 0);
		}
	}

	load_stat_from_file();
	cs_writeunlock(__func__, &system_lock);
}

void kill_all_readers(void)
{
	struct s_reader *rdr;
	for(rdr = first_active_reader; rdr; rdr = rdr->next)
	{
		struct s_client *cl = rdr->client;
		if(!cl)
			{ continue; }
		rdr_log(rdr, "Killing reader");
		kill_thread(cl);
#ifdef CS_CACHEEX_AIO
			ll_destroy_data(&cl->ll_cacheex_stats);
#endif
	}
	first_active_reader = NULL;
}

int32_t reader_slots_available(struct s_reader *reader, ECM_REQUEST *er)
{
	if(ecm_ratelimit_check(reader, er, 0) != OK) // check ratelimiter & cooldown -> in check mode: dont register srvid!!!
	{
		return 0; // no slot free
	}
	else
	{
		return 1; // slots available!
	}
}
